//app.asar 路径问题
import fs from 'original-fs';
import crypto from 'crypto';
import path from 'path';
import { promisify } from 'util';
import { spawn, exec } from 'child_process';

/**
 * 是否是开发模式
 */
export const isDev = process.env.NODE_ENV !== 'production';

Date.prototype.format = function (format) {
  var o = {
    'M+': this.getMonth() + 1, //month
    'd+': this.getDate(), //day
    'h+': this.getHours(), //hour
    'm+': this.getMinutes(), //minute
    's+': this.getSeconds(), //second
    'q+': Math.floor((this.getMonth() + 3) / 3), //quarter
    S: this.getMilliseconds(), //millisecond
  };
  if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
  for (var k in o)
    if (new RegExp('(' + k + ')').test(format))
      format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length));
  return format;
};

Promise.reduce = (iterable, reducer, initVal) =>
  new Promise((resolve, reject) => {
    const iterator = iterable[Symbol.iterator]();
    let i = 0;

    const next = (total) => {
      const el = iterator.next();

      if (el.done) {
        resolve(total);
        return;
      }

      Promise.all([total, el.value])
        .then((value) => {
          next(reducer(value[0], value[1], i++));
        })
        .catch(reject);
    };

    next(initVal);
  });

Promise.each = (iterable, iterator) => Promise.reduce(iterable, (a, b, i) => iterator(b, i)).then(() => iterable);

export const existsPromisify = promisify(fs.exists);

export const loger = console;

if (!isDev) {
  loger.debug = () => { };
}

/**
 * 文件md5
 * @param {*} file
 */
export const fileMd5 = (file) => {
  if (!fs.existsSync(file)) {
    return Promise.resolve();
  }
  return new Promise((resolve) => {
    const stream = fs.createReadStream(file);
    const hash = crypto.createHash('md5');
    stream.on('data', hash.update.bind(hash));
    stream.on('end', () => resolve(hash.digest('hex')));
  });
};

/**
 * 是否是字符串
 * @param {*} str
 */
export const isString = (str) => typeof str === 'string';

/**
 *是否是函数
 */
export const isFunction = () => typeof fn === 'function';

/**
 * 是否为空置
 * @param {*} obj
 */
export const isEmpty = (obj) => [Object, Array].includes((obj || {}).constructor) && !Object.entries(obj || {}).length;
/**
 * 资源路劲
 * @param {*} file
 */
export const asset = (file) => path.join(__dirname, `${isDev ? '../' : ''}assets/${file}`);

const _0777 = parseInt('0777', 8);

// fs.mkdirSync(path[, options]) v10.12.0 recursive
/**
 *创建文件路径
 * @param {*} p
 * @param {*} opts
 */
export const mkdirSync = (p, opts, made) => {
  if (!opts || typeof opts !== 'object') {
    opts = { mode: opts };
  }

  var mode = opts.mode;
  var xfs = opts.fs || fs;

  if (mode === undefined) {
    mode = _0777 & ~process.umask();
  }
  if (!made) made = null;

  p = path.resolve(p);

  try {
    xfs.mkdirSync(p, mode);
    made = made || p;
  } catch (err0) {
    switch (err0.code) {
      case 'ENOENT':
        made = mkdirSync(path.dirname(p), opts, made);
        mkdirSync(p, opts, made);
        break;

      // In the case of any other error, just see if there's a dir
      // there already.  If so, then hooray!  If not, then something
      // is borked.
      default:
        var stat;
        try {
          stat = xfs.statSync(p);
        } catch (err1) {
          throw err0;
        }
        if (!stat.isDirectory()) throw err0;
        break;
    }
  }

  return made;
};

const defmode = _0777 & ~process.umask();
const chmodSync = (p) => fs.chmodSync(p, defmode);
/**
 * 删除文件
 */
export const unlinkSync = (filePath) => {
  if (fs.existsSync(filePath)) {
    try {
      fs.unlinkSync(filePath);
    } catch (error) {
      if (error && error.code === 'EPERM') {
        chmodSync(filePath);
        fs.unlinkSync(filePath);
      } else {
        throw error;
      }
    }
  }
};

/**
 * 删除目录
 * @param {*} p
 */
export const rmdirSync = (p) => {
  if (!fs.existsSync(p)) return;
  const stat = fs.lstatSync(p);
  if (stat.isFile()) {
    unlinkSync(p);
  } else if (stat.isDirectory()) {
    const files = fs.readdirSync(p);
    files.forEach((file) => {
      rmdirSync(path.join(p, file));
    });
    if (fs.existsSync(p)) {
      try {
        fs.rmdirSync(p);
      } catch (error) {
        if (error && error.code === 'EPERM') {
          chmodSync(p);
          fs.rmdirSync(p);
        } else {
          throw error;
        }
      }
    }
  }
};
/**
 * 移动文件
 * @param {*} src
 * @param {*} dest
 */
export const renameSync = (src, dest) => {
  mkdirSync(path.dirname(dest));
  try {
    fs.renameSync(src, dest);
    return true;
  } catch (error) {
    if (fs.existsSync(src)) {
      fs.copyFileSync(src, dest);
      unlinkSync(src);
      return true;
    }
  }
};

export const noop = () => { };
/**
 * 分离式启动
 * @param {*} exe
 * @param {*} args
 * @param {*} param2
 */
export const spawnDetached = (exe, args, { onExit = noop, ...options } = {}) => {
  return new Promise((resolve, reject) => {
    try {
      const process = spawn(exe, args, {
        detached: true,
        stdio: 'ignore',
        ...options,
      });
      process.on('exit', (code) => {
        if (onExit) onExit(code);
      });
      process.on('error', (error) => {
        reject(error);
      });
      process.unref();
      if (process.pid !== undefined) {
        resolve(true);
      }
    } catch (error) {
      reject(error);
    }
  });
};

/**
 * 监护式启动
 * @param {*} cmd
 * @param {*} args
 * @param {*} options
 * @param {*} ctrls
 */
export const spawnPromise = (cmd, args, options, ctrls = {}) => {
  return new Promise((resolve, reject) => {
    const { log = console, endMsg } = ctrls;
    const sp = spawn(cmd, args, options);
    sp.stdout.on('data', (data) => {
      const res = `${data}`;
      log.debug('stdout', res);
      if (endMsg && res.indexOf(endMsg) >= 0) {
        reject(2);
      }
    });
    sp.stderr.on('data', (data) => {
      log.debug('stderr', data);
    });
    sp.on('exit', (code) => {
      resolve(code);
    });
  });
};
export const sleep = (time = 500) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });

export const execPromise = (cmd, options = { encoding: 'utf8' }) =>
  new Promise((resolve, reject) => {
    exec(cmd, options, (err, stdout, stderr) => {
      if (err) {
        reject(err);
      } else {
        resolve({ stdout, stderr });
      }
    });
  });
export const wmicProcessByPath = (exe, cmd = '', opts) => {
  return execPromise(`wmic process where executablepath="${exe.replace(/\\/g, '\\\\')}" ${cmd}`, opts).catch(() => { });
};

export const killByPath = (exe) => {
  return wmicProcessByPath(exe, 'delete');
};
const pathSymbol = Symbol('path');
/**
 * 获取文件夹结构
 * @param {*} dir
 * @param {*} onlyFile
 */
export const readDirFiles = async (dir, { onlyFile, rootDir } = {}) => {
  rootDir = rootDir || dir;
  const items = fs.existsSync(dir) ? fs.readdirSync(dir, { withFileTypes: true }) : [];
  const res = items.reduce(
    (res, item) => {
      const { name } = item;
      const filePath = path.join(dir, name);
      const pathRelative = path.relative(rootDir, filePath).replace(/\\/g, '/');
      if (item.isDirectory()) {
        res.dirs.push({ [pathSymbol]: filePath, name: pathRelative });
      } else {
        res.files.push({ [pathSymbol]: filePath, name: pathRelative });
      }
      return res;
    },
    { name: path.relative(rootDir, dir).replace(/\\/g, '/'), files: [], dirs: [] },
  );
  if (res.dirs.length) {
    const dirInfos = await Promise.all(res.dirs.map(({ [pathSymbol]: item }) => readDirFiles(item, { rootDir })));
    for (let index = 0; index < res.dirs.length; index++) {
      res.dirs[index] = { ...res.dirs[index], ...dirInfos[index] };
    }
  } else {
    delete res.dirs;
  }
  if (!res.files.length) {
    delete res.files;
  }

  if (onlyFile) {
    return readFiles([res]);
  }
  return res;
};

const readFiles = (dirs) => {
  return dirs.reduce((res, { dirs, files = [] }) => {
    files.forEach(({ name }) => {
      res.push(name);
    });
    if (dirs) {
      res.push(...readFiles(dirs));
    }
    return res;
  }, []);
};

export const protocolName = 'hnszjc';


/**
 * 获取客户端启动参数
 * @param {*} args 
 * @returns 
 */
export const getProtocolUrl = args => {
  const prefix = `${protocolName}://`;
  return args.find((arg) => arg.startsWith(prefix));
}